using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.Modeling;

namespace DslCrossModelFramework.NameResolution {
    public class NameResolutionManager {

        private static NameResolutionManager currentManager;
        private static object syncObject = new object();
        public static NameResolutionManager Current {
            get {
                if (currentManager == null) {
                    lock (syncObject) {
                        if (currentManager == null) {
                            currentManager = new NameResolutionManager();
                        }
                    }
                }
                return currentManager;
            }
        }

        public NameResolutionManager() {
        }

        public void RegisterNameResolver(IModelElementNameResolver nameResolver) {
            foreach (IModelElementNameResolver resolver in globalModelNameResolvers) {
                if(resolver.ModelElementType == nameResolver.ModelElementType && resolver.GetType() != nameResolver.GetType()) {
                    throw new NameResolutionAlreadyDefinedException(nameResolver.ModelElementType);
                }
            }
            globalModelNameResolvers.Add(nameResolver);
        }

        public void RegisterNameResolver(IModelElementNameResolver nameResolver, string scope) {
            if (string.IsNullOrEmpty(scope)) {
                RegisterNameResolver(nameResolver);
                return;
            }
            if (!scopedModelNameResolvers.ContainsKey(scope)) {
                scopedModelNameResolvers.Add(scope, new List<IModelElementNameResolver>());
            }
            foreach (IModelElementNameResolver resolver in scopedModelNameResolvers[scope]) {
                if (resolver.ModelElementType == nameResolver.ModelElementType && resolver.GetType() != nameResolver.GetType()) {
                    throw new NameResolutionAlreadyDefinedException(nameResolver.ModelElementType, scope);
                }
            }
            scopedModelNameResolvers[scope].Add(nameResolver);
        }


        private List<IModelElementNameResolver> globalModelNameResolvers = new List<IModelElementNameResolver>();
        private Dictionary<string, List<IModelElementNameResolver>> scopedModelNameResolvers = new Dictionary<string, List<IModelElementNameResolver>>();

        public List<IModelElementNameResolver> GlobalModelRelationships {
            get { return globalModelNameResolvers; }
        }
        public Dictionary<string, List<IModelElementNameResolver>> ScopedModelRelationships {
            get { return scopedModelNameResolvers; }
        }

        public string GetElementName(ModelElement mel) {
            return GetElementName(mel, string.Empty);
        }

        public string GetElementName(ModelElement mel, string scope) {
            return GetResolver(mel.GetDomainClass(), scope).GetName(mel);
        }

        public IModelElementNameResolver GetResolver(DomainClassInfo domainClassInfo, string scope) {
            if (string.IsNullOrEmpty(scope)) {
                foreach (IModelElementNameResolver resolver in globalModelNameResolvers) {
                    if (resolver.ModelElementType == domainClassInfo.ImplementationClass) {
                        return resolver;
                    }
                }
            }
            else {
                if (scopedModelNameResolvers.ContainsKey(scope)) {
                    foreach (IModelElementNameResolver resolver in scopedModelNameResolvers[scope]) {
                        if (resolver.ModelElementType == domainClassInfo.ImplementationClass) {
                            return resolver;
                        }
                    }
                }
            }

            if(!string.IsNullOrEmpty(scope) ) {
                return GetResolver(domainClassInfo, string.Empty);
            }
            return new DefaultModelElementNameResolver();
        }

        public IModelElementNameResolver GetResolver(DomainClassInfo info) {
            return GetResolver(info, string.Empty);
        }

        public IModelElementNameResolver GetResolver(ModelElement mel) {
            return GetResolver(mel, string.Empty);
        }

        public IModelElementNameResolver GetResolver(ModelElement mel, string scope) {
            return GetResolver(mel.GetDomainClass(), scope);
        }

        public void ClearRegistrations() {
            globalModelNameResolvers.Clear();
            scopedModelNameResolvers.Clear();
        }
    }

    public class NameResolutionAlreadyDefinedException : Exception {
        public NameResolutionAlreadyDefinedException(Type modelType) : base(string.Format("A name resolver is already defined for the domain model type '{0}'", modelType.FullName)) { }
        public NameResolutionAlreadyDefinedException(Type modelType, string scope) : base(string.Format("A name resolver is already defined for the domain model type '{0}' in scope '{1}'", modelType.FullName, scope)) { }
    }

    public class NameResolutionException : Exception {
        public NameResolutionException(string message) : base(message) {}
        public NameResolutionException(string message, Exception inner) : base(message, inner) { }
    }
}